package nig.ssh;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.KnownHosts;
import ch.ethz.ssh2.ServerHostKeyVerifier;
import ch.ethz.ssh2.Session;
import java.io.IOException;
import static nig.ssh.Util.base64Encode;
import static nig.ssh.Util.illegal;

/**
 * Utility class for common SSH operations.
 * 
 * @author King Lung Chiu
 */
public final class SSH {  

   /**
    * Create a new authenticated SSH {@link Connection} object using the default
    * SSH port. All parameters
    * are required.
    * 
    * @param host                remote server's address
    * @param hostKeyFingerprint  remote server's public key fingerprint
    * @param username            username to authenticate with
    * @param privateKey          user's private key to authenticate with
    * 
    * @return
    *    An authenticated {@link Connection} object.
    * 
    * @throws IOException
    *    If connection setup fails.
    */
   public static Connection authenticatedConnection(
      String host,
      String hostKeyFingerprint,
      String username,
      char[] privateKey
   ) throws IOException {
      return authenticatedConnection(host, null, hostKeyFingerprint, username, privateKey);
   }


   /**
    * Create a new authenticated SSH {@link Connection} object. All parameters
    * are required. If port is set to null or a value less than 1 is supplied,
    * then the default SSH port will be used.
    * 
    * @param host                remote server's address
    * @param port                remote server's port
    * @param hostKeyFingerprint  remote server's public key fingerprint
    * @param username            username to authenticate with
    * @param privateKey          user's private key to authenticate with
    * 
    * @return
    *    An authenticated {@link Connection} object.
    * 
    * @throws IOException
    *    If connection setup fails.
    */
   public static Connection authenticatedConnection(
      String host,
      Short  port,
      String hostKeyFingerprint,
      String username,
      char[] privateKey
   ) throws IOException {
      if(host == null)                 illegal("host cannot be null");
      if(host.isEmpty())               illegal("host cannot be empty");
      if(hostKeyFingerprint == null)   illegal("hostKeyFingerprint cannot be null");
      if(hostKeyFingerprint.isEmpty()) illegal("hostKeyFingerprint cannot be empty");
      if(username == null)             illegal("username cannot be null");
      if(username.isEmpty())           illegal("username cannot be empty");
      if(privateKey == null)           illegal("privateKey cannot be null");
      if(privateKey.length == 0)       illegal("privateKey cannot be empty (zero length)");

      Connection con = (port == null || port < 1)?
         new Connection(host):
         new Connection(host, port);
      con.connect(hostKeyVerifier(hostKeyFingerprint));
      con.authenticateWithPublicKey(username, privateKey, null);
      return con;
   }


   public static ServerHostKeyVerifier hostKeyVerifier(
      final String hostKeyFingerprint
   ) {
      if(hostKeyFingerprint == null) illegal("hostKeyFingerprint cannot be null");

      return new ServerHostKeyVerifier() {
         @Override
         public boolean verifyServerHostKey(
            String hostname, int port,
            String serverHostKeyAlgorithm,
            byte[] serverHostKey) throws Exception
         {
            String peerFingerprint = KnownHosts.createHexFingerprint(
               serverHostKeyAlgorithm, serverHostKey);
            System.out.println(
               "server host key info:\n" +
               " host: " + hostname + ",\n" +
               " port: " + port + ",\n" +
               " algo: " + serverHostKeyAlgorithm + ",\n" +
               "  key: " + base64Encode(serverHostKey) + "\n" +
               " fprt (expected): " + hostKeyFingerprint + "\n" +
               " fprt (actual)  : " + peerFingerprint + "\n"
            );
            return hostKeyFingerprint.equals(peerFingerprint);
         }
      };
   }


   /**
    * Execute a given command on a remote host.
    * 
    * @param con  The SSH connection for the remote host.
    * @param cmd  The command to execute.
    * @throws IOException If any connection error occurs.
    */
   public static void exec(Connection con, String cmd) throws IOException {
      Session ssh = con.openSession();
      ssh.execCommand(cmd);
      ssh.close();
   }

   
   /**
    * Create the specified directory - on a remote host - and all its parents
    * if they don't already exist. Method does nothing if an empty path is
    * supplied.
    * 
    * @param con  The SSH connection for the remote host.
    * @param path The directory to create.
    * @throws IOException If any connection error occurs.
    */
   public static void mkdirs(Connection con, String path) throws IOException {
      if(con == null)   illegal("con cannot be null");
      if(path == null)  illegal("path cannot be null");
      
      Session ssh = null;
      try {
         ssh = con.openSession();
         mkdirs(ssh, path);
      } finally {
         if(ssh != null)
            ssh.close();
      }
   }
//   public static void mkdirs(Connection con, String path) throws IOException {
//      if(con == null)   illegal("con cannot be null");
//      if(path == null)  illegal("path cannot be null");
//      
//      if(!path.isEmpty())
//         exec(con, mkdirsCmd(path));
//   }
   
   /**
    * Create the specified directory - on a remote host - and all its parents
    * if they don't already exist. Method does nothing if an empty path is
    * supplied.
    * 
    * @param ssh  The SSH session for the remote host.
    * @param path The directory to create.
    * @throws IOException If any connection error occurs.
    */
   public static void mkdirs(Session ssh, String path) throws IOException {
      if(ssh == null)   illegal("ssh cannot be null");
      if(path == null)  illegal("path cannot be null");
      
      if(!path.isEmpty()) {
         ssh.execCommand(mkdirsCmd(path));
         ssh.waitForCondition(ChannelCondition.EOF, 0);
      }
   }   
   private static String mkdirsCmd(String path) {
//      return String.format("test -d %s || mkdir -p %s", path, path);
      return "mkdir -p \"" + path + "\""; // double-quote to preserve white spaces
   }
}